package de.akabahn.ebd.anlage;

import java.beans.XMLDecoder;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections4.map.CaseInsensitiveMap;

/**
 * Represents the static configuration of the EBD Anlage.
 *
 * @author moschlar
 */
public class Anlage implements Serializable {

    private Map<String, String> ds100;
    private Map<String, Betriebsstelle> betriebsstellen;
    private Map<String, Abschnitt> abschnitte;
    private Map<String, Signal> signale;

    public static void main(String[] args) {
        // TEST
        System.out.println(fromXML());
    }

    public static Anlage fromXML() {
        return fromXML(Anlage.class.getClassLoader().getResourceAsStream("anlage.xml"));
    }

    public static Anlage fromXML(String filename) throws FileNotFoundException {
        return fromXML(new FileInputStream(filename));
    }

    public static Anlage fromXML(InputStream stream) {
        XMLDecoder xmlDecoder = new XMLDecoder(stream);
        return (Anlage) xmlDecoder.readObject();
    }

    public Anlage() {
        this.ds100 = new CaseInsensitiveMap<String, String>();
        this.betriebsstellen = new CaseInsensitiveMap<String, Betriebsstelle>();
        this.abschnitte = new CaseInsensitiveMap<String, Abschnitt>();
        this.signale = new CaseInsensitiveMap<String, Signal>();
    }

    public Anlage(Map<String, String> ds100, Map<String, Betriebsstelle> betriebsstellen, Map<String, Abschnitt> abschnitte, Map<String, Signal> signale) {
        this.ds100 = ds100;
        this.betriebsstellen = betriebsstellen;
        this.abschnitte = abschnitte;
        this.signale = signale;
    }

    // <editor-fold defaultstate="collapsed" desc="Getter/Setter">
    public Map<String, String> getDs100() {
        return ds100;
    }

    public void setDs100(Map<String, String> ds100) {
        this.ds100 = ds100;
    }

    public Map<String, Betriebsstelle> getBetriebsstellen() {
        return betriebsstellen;
    }

    public void setBetriebsstellen(Map<String, Betriebsstelle> betriebsstellen) {
        this.betriebsstellen = betriebsstellen;
    }

    public Map<String, Abschnitt> getAbschnitte() {
        return abschnitte;
    }

    public void setAbschnitte(Map<String, Abschnitt> abschnitte) {
        this.abschnitte = abschnitte;
    }

    public Map<String, Signal> getSignale() {
        return signale;
    }

    public void setSignale(Map<String, Signal> signale) {
        this.signale = signale;
    }
    // </editor-fold>

    @Override
    public String toString() {
        return String.format("<Anlage: %d Betriebsstellen, %d Abschnitte, %d Signale>",
                this.betriebsstellen.size(),
                this.abschnitte.size(),
                this.signale.size()
        );
    }

    public boolean hasBetriebsstelle(String s) {
        return this.getBetriebsstelle(s) != null;
    }

    /**
     * Get a specific Betriebsstelle from the Anlage.
     *
     * @param s name of the Betriebsstelle either as DS100 or full name
     * @return the wanted Betriebsstelle or null if none could be found
     */
    public Betriebsstelle getBetriebsstelle(String s) {
        Betriebsstelle betriebsstelle;
        betriebsstelle = getBetriebsstelleByDS100(s);
        if (betriebsstelle != null) return betriebsstelle;
        betriebsstelle = getBetriebsstelleByName(s);
        if (betriebsstelle != null) return betriebsstelle;
        return null;
    }

    private Betriebsstelle getBetriebsstelleByDS100(String ds100) {
        return this.betriebsstellen.get(ds100);
    }

    private Betriebsstelle getBetriebsstelleByName(String name) {
        name = this.ds100.get(name);
        if (name != null) {
            return this.betriebsstellen.get(name);
        } else {
            return null;
        }
    }

    private Betriebsstelle getBetriebsstelleByPrefix(String prefix) {
        for (String name : this.ds100.values()) {
            if ((name != null) && (name.startsWith(prefix))) {
                return getBetriebsstelleByName(name);
            }
        }
        return null;
    }

    private Betriebsstelle getBetriebsstelleByRegex(String regex) {
        for (String name : this.ds100.values()) {
            if ((name != null) && (name.matches(regex))) {
                return getBetriebsstelleByName(name);
            }
        }
        return null;
    }

    public List<Betriebsstelle> getBetriebsstellenSorted() {
        List<Betriebsstelle> betriebsstellen = new ArrayList<Betriebsstelle>(this.getBetriebsstellen().values());
        Collections.sort(betriebsstellen, new Comparator<Betriebsstelle>() {
            @Override
            public int compare(Betriebsstelle a, Betriebsstelle b) {
                return a.getName().compareToIgnoreCase(b.getName());
            }
        });
        return betriebsstellen;
    }

    /**
     * Get a specific Abschnitt from the Anlage.
     *
     * @param s name of the Abschnitt
     * @return the wanted Abschnitt or null if none could be found
     */
    public Abschnitt getAbschnitt(String s) {
        return this.abschnitte.get(s);
    }

    /**
     * Get a specific Signal from the Anlage.
     *
     * @param b the Betriebsstelle in which to look for
     * @param s the name of the Signal
     * @return the wanted Signal or null if none could be found
     */
    public Signal getSignal(Betriebsstelle b, String s) {
        String name = String.format("%02d%s", b.getNummer(), s);
        return this.getSignal(name);
    }

    /**
     * Get a specific Signal from the Anlage.
     *
     * @param s the full name of the Signal
     * @return the wanted Signal or null if none could be found
     */
    public Signal getSignal(String s) {
        if (this.signale.containsKey(s)) {
            return this.signale.get(s);
        }
        return null;
    }

}
